import logging
import json
import redis

from django.contrib.auth.models import User
from django.conf import settings
from django.conf.urls import url
from django.shortcuts import get_object_or_404
from django.core.cache import cache

from tastypie import fields
from tastypie.authorization import DjangoAuthorization
from tastypie.constants import ALL_WITH_RELATIONS, ALL
from tastypie.resources import ModelResource
from tastypie.http import HttpCreated, HttpApplicationError
from tastypie.utils import dict_strip_unicode_keys, trailing_slash

from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Build, Version
from readthedocs.core.utils import trigger_build
from readthedocs.projects.models import Project, ImportedFile
from readthedocs.restapi.views.footer_views import get_version_compare_data

from .utils import SearchMixin, PostAuthentication

log = logging.getLogger(__name__)


class ProjectResource(ModelResource, SearchMixin):
    users = fields.ToManyField('readthedocs.api.base.UserResource', 'users')

    class Meta(object):
        include_absolute_url = True
        allowed_methods = ['get', 'post', 'put']
        queryset = Project.objects.api()
        authentication = PostAuthentication()
        authorization = DjangoAuthorization()
        excludes = ['path', 'featured', 'programming_language']
        filtering = {
            "users": ALL_WITH_RELATIONS,
            "slug": ALL_WITH_RELATIONS,
        }

    def get_object_list(self, request):
        self._meta.queryset = Project.objects.api(user=request.user)
        return super(ProjectResource, self).get_object_list(request)

    def dehydrate(self, bundle):
        bundle.data['downloads'] = bundle.obj.get_downloads()
        return bundle

    def post_list(self, request, **kwargs):
        """
        Creates a new resource/object with the provided data.

        Calls ``obj_create`` with the provided data and returns a response
        with the new resource's location.

        If a new resource is created, return ``HttpCreated`` (201 Created).
        """
        deserialized = self.deserialize(
            request, request.body,
            format=request.META.get('CONTENT_TYPE', 'application/json')
        )

        # Force this in an ugly way, at least should do "reverse"
        deserialized["users"] = ["/api/v1/user/%s/" % request.user.id]
        bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)
        self.is_valid(bundle)
        updated_bundle = self.obj_create(bundle, request=request)
        return HttpCreated(location=self.get_resource_uri(updated_bundle))

    def sync_versions(self, request, **kwargs):
        """
        Sync the version data in the repo (on the build server) with what we have in the database.

        Returns the identifiers for the versions that have been deleted.
        """
        project = get_object_or_404(Project, pk=kwargs['pk'])
        try:
            post_data = self.deserialize(
                request, request.body,
                format=request.META.get('CONTENT_TYPE', 'application/json')
            )
            data = json.loads(post_data)
            self.method_check(request, allowed=['post'])
            self.is_authenticated(request)
            self.throttle_check(request)
            self.log_throttled_access(request)
            self._sync_versions(project, data['tags'])
            self._sync_versions(project, data['branches'])
            deleted_versions = self._delete_versions(project, data)
        except Exception as e:
            return self.create_response(
                request,
                {'exception': e.message},
                response_class=HttpApplicationError,
            )
        return self.create_response(request, deleted_versions)

    def override_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name,
                self.wrap_view('get_schema'), name="api_get_schema"),
            url(r"^(?P<resource_name>%s)/search%s$" % (
                self._meta.resource_name, trailing_slash()),
                self.wrap_view('get_search'), name="api_get_search"),
            url(r"^(?P<resource_name>%s)/(?P<pk>\d+)/sync_versions%s$" % (
                self._meta.resource_name, trailing_slash()),
                self.wrap_view('sync_versions'), name="api_sync_versions"),
            url((r"^(?P<resource_name>%s)/(?P<slug>[a-z-_]+)/$")
                % self._meta.resource_name, self.wrap_view('dispatch_detail'),
                name="api_dispatch_detail"),
        ]


class VersionResource(ModelResource):
    project = fields.ForeignKey(ProjectResource, 'project', full=True)

    class Meta(object):
        allowed_methods = ['get', 'put', 'post']
        always_return_data = True
        queryset = Version.objects.api()
        authentication = PostAuthentication()
        authorization = DjangoAuthorization()
        filtering = {
            "project": ALL_WITH_RELATIONS,
            "slug": ALL_WITH_RELATIONS,
            "active": ALL,
        }

    def get_object_list(self, request):
        self._meta.queryset = Version.objects.api(user=request.user)
        return super(VersionResource, self).get_object_list(request)

    def version_compare(self, request, project_slug, base=None, **kwargs):
        project = get_object_or_404(Project, slug=project_slug)
        if base and base != LATEST:
            try:
                base_version = project.versions.get(slug=base)
            except (Version.DoesNotExist, TypeError):
                base_version = None
        else:
            base_version = None
        ret_val = get_version_compare_data(project, base_version)
        return self.create_response(request, ret_val)

    def build_version(self, request, **kwargs):
        project = get_object_or_404(Project, slug=kwargs['project_slug'])
        version = kwargs.get('version_slug', LATEST)
        version_obj = project.versions.get(slug=version)
        trigger_build(project=project, version=version_obj)
        return self.create_response(request, {'building': True})

    def override_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/schema/$"
                % self._meta.resource_name,
                self.wrap_view('get_schema'),
                name="api_get_schema"),
            url((r"^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+)/highest/"
                 r"(?P<base>.+)/$")
                % self._meta.resource_name,
                self.wrap_view('version_compare'),
                name="version_compare"),
            url(r"^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+)/highest/$"
                % self._meta.resource_name,
                self.wrap_view('version_compare'),
                name="version_compare"),
            url(r"^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+[a-z0-9-_]+)/$"  # noqa
                % self._meta.resource_name,
                self.wrap_view('dispatch_list'),
                name="api_version_list"),
            url((r"^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+)/(?P"
                 r"<version_slug>[a-z0-9-_.]+)/build/$")
                % self._meta.resource_name,
                self.wrap_view('build_version'),
                name="api_version_build_slug"),
        ]


class FileResource(ModelResource, SearchMixin):
    project = fields.ForeignKey(ProjectResource, 'project', full=True)

    class Meta(object):
        allowed_methods = ['get', 'post']
        queryset = ImportedFile.objects.all()
        excludes = ['md5', 'slug']
        include_absolute_url = True
        authentication = PostAuthentication()
        authorization = DjangoAuthorization()
        search_facets = ['project']

    def override_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/schema/$" %
                self._meta.resource_name,
                self.wrap_view('get_schema'),
                name="api_get_schema"),
            url(r"^(?P<resource_name>%s)/search%s$" %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('get_search'),
                name="api_get_search"),
            url(r"^(?P<resource_name>%s)/anchor%s$" %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('get_anchor'),
                name="api_get_anchor"),
        ]

    def get_anchor(self, request, **kwargs):
        self.method_check(request, allowed=['get'])
        self.is_authenticated(request)
        self.throttle_check(request)

        query = request.GET.get('q', '')
        try:
            redis_client = cache.get_client(None)
            redis_data = redis_client.keys("*redirects:v4*%s*" % query)
        except (AttributeError, redis.exceptions.ConnectionError):
            redis_data = []
        # -2 because http:
        urls = [''.join(data.split(':')[6:]) for data in redis_data
                if 'http://' in data]
        object_list = {'objects': urls}

        self.log_throttled_access(request)
        return self.create_response(request, object_list)


class UserResource(ModelResource):

    class Meta(object):
        allowed_methods = ['get']
        queryset = User.objects.all()
        fields = ['username', 'first_name', 'last_name', 'last_login', 'id']
        filtering = {
            'username': 'exact',
        }

    def override_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/schema/$" %
                self._meta.resource_name,
                self.wrap_view('get_schema'),
                name="api_get_schema"),
            url(r"^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$" %
                self._meta.resource_name,
                self.wrap_view('dispatch_detail'),
                name="api_dispatch_detail"),
        ]
